﻿using System;

namespace GameOfLife {

    /// <summary>
    /// Class representing a Game-of-Life instance
    /// </summary>
    class Game {
        private int?[,] board;
        private CellState[] cells;
        private static int N;
        private Random rand = new Random();
        private int k;

        /// <summary>
        /// Creates the board with n * n cells. The cell-struct are given a x,y value and a cell-state.
        /// </summary>
        /// <param name="n">Size if the board</param>
        public Game(int n) {
            board = new int?[n, n];
            cells = new CellState[n * n];
            k = 0;
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    CellState cell = new CellState(i, j, rand.Next(0,2));
                    cells[k] = cell;
                    k++;
                }
            }
            foreach (CellState cs in cells) {
                board[cs.x, cs.y] = cs.state;
            }
        }

        /// <summary>
        /// Contructor
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args) {
            N = int.Parse(args[0]);
            Game game = new Game(N);
            int run = 1;
            while (true) {
                Console.WriteLine("Run: " + run + "\t(press ANY KEY to quit)");
                Console.WriteLine("-------------------------------");
                run++;

                game.draw();
                game.ChangeState(game.getCells());

                if (Console.KeyAvailable) break;
                System.Threading.Thread.Sleep(1000);
                //Console.ReadKey();
                Console.Clear();
            }
        }

        /// <summary>
        /// Changes the state of the cell, depending on the state of its neighbours.
        /// </summary>
        /// <param name="cells">Cells in the array</param>
        void ChangeState(params CellState[] cells) {
            for (int i = 0; i < cells.Length; i++) {
                //Create a temp cell to modify
                CellState tempCell = cells[i];
                int nabo = CountNabo(tempCell.x, tempCell.y);
                //if dead
                if (tempCell.state == 0) {
                    if (nabo >= 3) {
                        tempCell.state = 1;
                        //Copy the cell back into the array
                        cells[i] = tempCell;
                    }
                }
                //if alive
                else if (nabo >= 1) {
                    if (rand.Next(2) >= 1) {
                        tempCell.state = null;
                        cells[i] = tempCell;
                    }
                }
                else if (nabo <= 1) {
                    tempCell.state = 0;
                    //Copy the cell back into the array
                    cells[i] = tempCell;
                }
                else if (nabo <= 3) {
                    tempCell.state = 1;
                    //Copy the cell back into the array
                    cells[i] = tempCell;
                }
                else {
                    tempCell.state = 0;
                    //Copy the cell back into the array
                    cells[i] = tempCell;
                }
            }
            // update the board
            foreach (CellState cs in cells) {
                board[cs.x, cs.y] = cs.state;
            }
        }

        private CellState[] getCells() {
            return this.cells;
        }

        /// <summary>
        /// Draws the board in the cmd from the cell array. Changes the line in the cmd
        /// if the counter % N is equal to zero.
        /// </summary>
        private void draw() {
            int counter = 1;
            foreach (int? cell in board) {
                if (counter % N == 0) Console.WriteLine(cell);
                else Console.Write(cell + " ");
                counter++;
            }
        }

        /// <summary>
        /// Counts the neighbours to the given cell. If the neighbour cell is out of
        /// the board index, a IndexOutOfRangeException is caught.
        /// </summary>
        /// <param name="x">x value</param>
        /// <param name="y">y value</param>
        /// <returns></returns>
        private int CountNabo(int x, int y) {
            int counter = 0;
            for (int i = -1; i < 2; i++)
                for (int j = -1; j < 2; j++)
                    try {
                        if (i == 0 && j == 0) continue;
                        if (board[x + i, y + j] == 1)
                            counter++;
                    }
                    catch (IndexOutOfRangeException) {
                        //Console.WriteLine(i + "," + j);
                    }
            return counter;
        }


        class OutOfBoardExcption : Exception {
            public OutOfBoardExcption(string e) : base(e) { }

        }

        /// <summary>
        /// The struct of a cell. It contains a contructor which initialize
        /// the x,y and state value.
        /// </summary>
        struct CellState {

            public CellState(int x, int y, int? state)
                : this() {
                this.x = x;
                this.y = y;
                this.state = state;
            }

            public int x {
                set;
                get;
            }
            public int y {
                set;
                get;
            }
            public int? state {
                set;
                get;
            }
        }
    }
}
